package tuple;

import pojo.MyTuple;
import tuple.data.DataKind;
import tuple.entity.MyEntity;

import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


public class TupleManager {
    public String apkName;

    public enum State {
        SCAN, FLOW, COND, NONE
    }

    public State phase;
    public Set<MyTuple> tuples;


    // 列索引 entity data permissions
    private Set<MyEntity> entity;
    private Set<DataKind> data;
    private Set<String> permissions;

    public String dumpDir;
    public static final List<DataKind> nonUserData = Arrays.stream(
            new DataKind[]{DataKind.SECURITY, DataKind.PERMISSION, DataKind.ANDROID_PERMISSION, DataKind.OTHERS, DataKind.ANY}
    ).toList();

    public TupleManager(String apkName, State phase, String dumpDir) {
        this.apkName = apkName;
        this.phase = phase;
        this.tuples = new HashSet<>();

        this.entity = new HashSet<>();
        this.data = new HashSet<>();
        this.permissions = new HashSet<>();

        this.dumpDir = dumpDir;
    }

    // normalization and create
    @Nullable
    public static MyTuple createMyTuple(Set<MyEntity> entity, Set<DataKind> data, Set<String> marks) {
        if (entity == null && data == null) return null;

        boolean isValidContent = normTupleContentAndJudge(entity, data, marks);
        return isValidContent ? new MyTuple(entity, data, marks) : null;
    }

    public static boolean normTupleContentAndJudge(Set<MyEntity> entity,
                                                   Set<DataKind> data, Set<String> marks) {
        // avoid null
        if (data == null)
            data = new HashSet<>();
        if (entity == null)
            entity = new HashSet<>();
        if (marks == null)
            marks = new HashSet<>();

        // jump the useless tuple with only one element
        boolean isUseful = judgeUseful(entity, data, marks);
        if (!isUseful)
            return false;

        normalization(entity, data, marks);
        return true;
    }

    private static void normalization(Set<MyEntity> entity, Set<DataKind> data, Set<String> permission) {
        if (entity.isEmpty())
            entity.add(MyEntity.app);
        if (entity.contains(MyEntity.android))
            entity.add(MyEntity.app);

        if (data.isEmpty())
            data.add(DataKind.ANY);
        else if (data.size() == 1 && data.contains(DataKind.ANY)) {
            // do nothing
        } else {
            data.remove(DataKind.ANY);
        }

        if (data.contains(DataKind.ANDROID_PERMISSION)) {
            permission.add("android.permission");
        } else if (data.contains(DataKind.PERMISSION)) {
            permission.add("permission");
        }
    }


    // true if entity/data is valid (has valid items)
    public static boolean judgeUseful(Set<MyEntity> entity, Set<DataKind> data, Set<String> marks) {
        long usefulDataSize = data.stream()
                .filter(x -> !nonUserData.contains(x))
                .count();
        long usefulEntitySize = entity.stream()
                .filter(x -> !x.equals(MyEntity.app) && !x.equals(MyEntity.android))
                .count();

        // 没有有效数据项 && 没有有效实体项 -> false 无效数据流
        if (usefulDataSize <= 0 && (usefulEntitySize <= 0 || marks.size() < 2))
            return false;

        return true;
    }

    public void addTuple(MyTuple tuple) {
        if (tuple != null) {
            if (tuple.data.isEmpty())
                tuple.data.add(DataKind.ANY);
            if (tuple.entity.isEmpty())
                tuple.entity.add(MyEntity.app);
            this.tuples.add(tuple);
        }
    }

    public void addTuple(Set<MyEntity> entity, Set<DataKind> data, Set<String> permission) {
        MyTuple tuple = createMyTuple(entity, data, permission);
        addTuple(tuple);
    }

    public String getMyTuple(Set<MyTuple> targetSet) throws RuntimeException {
        StringBuilder sb = new StringBuilder();
        for (MyTuple tuple : targetSet) {
            sb.append(tuple.toString());
            sb.append("\n");
        }
        return sb.toString();
    }

    public void clear() {
        this.entity.clear();
        this.data.clear();
        this.tuples.clear();
        this.phase = State.NONE;
    }

    public void prepareScan() {
        System.out.println("prepareScan");

        this.clear();
        this.phase = State.SCAN;
    }

    public void prepareTaintFlow() {
        System.out.println("prepareTaintFlow");

        this.clear();
        this.phase = State.FLOW;
    }

    public void syncIndexAfterChange() {
        this.entity = new HashSet<>();
        this.data = new HashSet<>();
        this.permissions = new HashSet<>();
        for (MyTuple tuple : this.tuples) {
            this.entity.addAll(tuple.entity);
            this.data.addAll(tuple.data);
            this.permissions.addAll(tuple.stringMarks);
        }
        this.data.remove(DataKind.ANY);
    }


    public Set<MyEntity> getEntity() {
        return entity;
    }

    public void setEntity(Set<MyEntity> entity) {
        this.entity = entity;
    }

    public Set<DataKind> getData() {
        return data;
    }

    public void setData(Set<DataKind> data) {
        this.data = data;
    }

    public Set<String> getPermissions() {
        return permissions;
    }

    public void setPermissions(Set<String> permissions) {
        this.permissions = permissions;
    }
}
